//uncomment this if you want to create an .exe file with jsc
/*import System;

if (WScript === undefined) {
	var WScript = {};
	WScript.Echo = function (str) { print(str); };
	var args = System.Environment.GetCommandLineArgs();
	WScript.Arguments = args;
	WScript.Quit = function (exitCode) {};
}*/
var debug = false;
var createVSDoc = true;

function Utils() {
	this.trim = function (str) {
		return str.replace(/^\s+/, '').replace(/\s+$/, '').replace(/\{/g, "").replace(/\}/, "").replace(/#/, "");
	};

	this.getParamToken = function (line) {
		var retVal = new Array();
		var str = this.getToken(line, "@param");
		// {String} callbackId description
		retVal["type"] = this.trim(str.substring(0, str.indexOf(" ") + 1));
		// callbackId description
		str = this.trim(str.substring(str.indexOf(" ") + 1));
		if (str.indexOf(" ") === -1) { // only callbackId
			retVal["name"] = this.trim(str);
		} else {
			retVal["name"] = this.trim(str.substring(0, str.indexOf(" ")));
			retVal["description"] = this.trim(str.substring(str.indexOf(" ") + 1));
		}
		return retVal;
	};

	this.getToken = function (line, tokenName) {
		if (line.indexOf("*/") === -1)
			return this.trim(line.substring(line.indexOf(tokenName) + tokenName.length));
		
		// found a comment end in this line... a bit more complex
		return this.trim(line.substring(line.indexOf(tokenName) + tokenName.length, line.indexOf("*/") - 1));
	};

	this.writeParams = function (outFileStream, params) {
		var isFirst = true;
		for (var i in params) {
			if (!isFirst)
				outFileStream.Write(", ");
			outFileStream.Write(params[i]["name"]);
			isFirst = false;
		}
	};
}

function JSDocParser(createVSDoc, debug) {
	var that = {};
	that.fs = new ActiveXObject("Scripting.FileSystemObject");
	that.utils = new Utils();
	that.debug = debug;
	that.createVSDoc = createVSDoc;
	that.objectList = new Array();
	
	that.getVSDoc = function (foundTokens, params) {
		var retVal = "";
		
		if (foundTokens["description"] !== undefined) {
			retVal += "/// <summary>" + foundTokens["description"].replace(/\r\n/gi, "") + "</summary>\r\n";
		}
		
		if (params.length !== 0) {
			for (var i in params) {
				var desc = params[i]["description"] || "";
				retVal += "/// <param name=\"" + params[i]["name"] + "\" type=\""+params[i]["type"]+"\">" + desc + "</param>\r\n";
			}
		}
		if (foundTokens["class"] !== undefined) {
			retVal += "/// <returns type=\"" + foundTokens["class"] + "\" />";
		}
		
		if (foundTokens["returns"] !== undefined) {
			retVal += "/// <returns type=\""+foundTokens["returns"]+"\" />";
		}
		
		return retVal;
	};
	
	that.getType = function(typestr) {
		if (typestr === "String")
			return "\"\"";
		else if (typestr === "Number")
			return "0";
		else if (typestr === "Array")
			return "[]";
		else if (typestr === "Boolean")
			return "true";
		else if (typestr === "Function")
			return "new Function() {}";
		else if (typestr === "RegExp")
			return "new RegExp()";
		return "new " + typestr + "()";
	};
	
	that.processFile = function (infile, outfile, createnew, path) {
		var gfs = that.fs;
		var a = gfs.GetFile(infile);
		var outFileStream = null;
		var out = {};
		if (createnew)
			outFileStream = that.fs.CreateTextFile(outfile);
		else
		{
			out = that.fs.GetFile(outfile);
			outFileStream = out.OpenAsTextStream(8, -2);
		}
		var textStream = a.OpenAsTextStream(1, -2);
		
		var params = new Array();
		var comment = "";
		var isInComment = false;
		var namespaceWritten = new Array();
		var line = 0;
		
		var tokens = new Array();
		tokens["class"] = "@class";
		tokens["scope"] = "@scope";
		tokens["type"] = "@type";
		tokens["name"] = "@name";
		tokens["memberof"] = "@memberOf";
		tokens["namespace"] = "@namespace";
		tokens["description"] = "@description";
		tokens["returns"] = "@returns";
		
		var foundtokens = new Array();
		var allTokensOfFile = new Array();
		var allParamsOfFile = new Array();
		var allCommentsOfFile = new Array();
		var currentComment = 0;
		
		while (!textStream.AtEndOfStream) {
			var textLine = textStream.ReadLine();
			++line;

			if (textLine.indexOf("/**") !== -1) { // found token
				// namespace = ""; // only once per file!
				for (var i in tokens)
					if (i !== "namespace")
						foundtokens[i] = undefined;
				params = new Array();
				comment = "";
				isInComment = true;
			} else if (!isInComment)
				continue;
			
			for (var i in tokens) {
				if (that.debug) {
					WScript.Echo(textLine);
					WScript.Echo("Trying " + i + "("+tokens[i]+"/"+textLine.indexOf(tokens[i])+")");
				}
				if (textLine.indexOf(tokens[i] + " ") !== -1)
					foundtokens[i] = that.utils.getToken(textLine, tokens[i]);
			}

			if (textLine.indexOf("@param ") !== -1) { // found params token
				params.push(that.utils.getParamToken(textLine));
			}
			
			if (textLine.indexOf("*/") !== -1) { // end comment
				isInComment = false;
				if (textLine.indexOf("/**") === -1) {
					comment += textLine.substring(textLine.indexOf("*/") + 2);
					comment += "*/";
				} else {
					comment += textLine.substring(textLine.indexOf("/**"), textLine.indexOf("*/") + 2);
				}
				if (that.debug)
					WScript.Echo(comment);
				
				if (foundtokens["name"] === undefined) { // try to find function name in next line... *ehem*
					var nextLine = textStream.readLine();
					if (nextLine.indexOf(" = ") !== -1) { // assume that the name before the "=" is the function name
						// probably: this.fn = function (xyz) {
						var tmp = that.utils.trim(nextLine.substring(0, nextLine.indexOf(" = ")));
						// now we got this.fn or simply fn
						if (tmp.indexOf(".") !== -1)
							tmp = tmp.substring(tmp.indexOf(".") + 1); 
							
						foundtokens["name"] = tmp;
						if (that.debug)
							WScript.Echo("Didn't find @name! Found token: " + foundtokens["name"]);
					} else if (nextLine.indexOf(" ") !== -1) {
						tmp = that.utils.trim(nextLine.substring(nextLine.indexOf(" ") + 1, nextLine.indexOf("(")));
						if (tmp !== undefined && tmp !== "" && tmp !== "function" && tmp !== "var") { // sanity test
							foundtokens["name"] = tmp;
							if (that.debug) {
								WScript.Echo("Line: " + tmp);
								WScript.Echo("Didn't find @name! Found token: " + foundtokens["name"]);
							}
						}
					}
				}
				/*********************************************************************/
				/* up to here only reading of tokens, save them to be written later  */
				/* sorry for my bad english                                          */
				/*********************************************************************/
				
			}
			
			if (isInComment)
				comment += textLine + "\r\n";
			else { // save tokens and comments
				if (that.debug)
					WScript.Echo("Saving Comment: " + currentComment);
				allTokensOfFile[currentComment] = new Array();
				allParamsOfFile[currentComment] = new Array();
				for (var u in foundtokens)
					allTokensOfFile[currentComment][u] = foundtokens[u];
				for (var o in params)
					allParamsOfFile[currentComment][o] = params[o];
				allCommentsOfFile[currentComment] = comment;
				++currentComment;
			}
		}
		textStream.Close();
		
		var isInScopeDef = false;
		var omitNextComma = false;
		var objectCreated = false;
		
		// write all tokens to file
		for (var i = 0; i < currentComment; ++i) {
			var currentToken = allTokensOfFile[i];
			var currentParam = allParamsOfFile[i];
			var cComment = allCommentsOfFile[i];
			omitNextComma = true;
			if (that.debug)
				for (var j in currentToken)
					WScript.Echo(currentToken[j]);
			if (currentToken["namespace"] !== undefined && namespaceWritten[currentToken["namespace"]] === undefined) {
				WScript.Echo("Writing Namespace...");
				outFileStream.WriteLine("if ("+currentToken["namespace"]+" === undefined) {");
				outFileStream.WriteLine(cComment);
				outFileStream.WriteLine("var " + currentToken["namespace"] + " = {};");
				outFileStream.WriteLine("}");
				namespaceWritten[currentToken["namespace"]] = true;
				continue;
			}
			if (currentToken["class"] !== undefined || currentToken["memberof"] !== undefined) {
				omitNextComma = false;
				
				var examplefile = a.Path.substring(0, a.Path.lastIndexOf("\\") + 1) + currentToken["class"] + ".meta.js";
				if (that.debug)
					WScript.Echo(examplefile);
				
				if (that.fs.FileExists(examplefile)) {
					cComment = cComment.replace(/\/\**/, "");
					outFileStream.WriteLine("/**");
					var exFile = that.fs.GetFile(examplefile);
					var exampleFileStream = exFile.OpenAsTextStream(1);
					outFileStream.WriteLine(" * @description ");
					while (!exampleFileStream.AtEndOfStream) {
						outFileStream.WriteLine(" * " + exampleFileStream.ReadLine());
					}
					outFileStream.WriteLine(" * ");
					outFileStream.WriteLine(cComment);
				} 
				else
					outFileStream.WriteLine(cComment);
				if (that.debug) {
					WScript.Echo("class: " + currentToken["class"]);
					WScript.Echo("scope: " + currentToken["scope"]);
					WScript.Echo("memberof: " + currentToken["memberof"]);
					WScript.Echo("namespace: " + currentToken["namespace"]);
				}
				var name = currentToken["class"];
				var classReturn = "";
				if (name !== undefined) {
					classReturn = "";
				}
				if (currentToken["returns"] !== undefined)
					classReturn = "return " + that.getType(currentToken["returns"]);
				if (!isInScopeDef)
					classReturn += ";\r\n";
				if (currentToken["memberof"] !== undefined) {
					name = currentToken["memberof"].replace(/#/, "");
					if (!objectCreated) {
						outFileStream.WriteLine(name + " = {};");
						objectCreated = true;
						that.objectList.push({ n : name, p : outfile });
					}
					if (!isInScopeDef)
						name = name + "." + currentToken["name"]; // was: .prototype.
					else
						name = currentToken["name"];
				}
				if (that.debug)
					WScript.Echo(name);
				
				var vsDoc = that.getVSDoc(currentToken, currentParam);
				
				if (currentToken["type"] === undefined || currentToken["class"] !== undefined) {
					
					if (!isInScopeDef)
						outFileStream.Write(name + " = function(");
					else
						outFileStream.Write(name + ": function(");
					
					that.utils.writeParams(outFileStream, currentParam);
					outFileStream.WriteLine(") {");
					if (that.createVSDoc) {
						//if (that.debug)
							WScript.Echo(vsDoc);
						//outFileStream.WriteLine
						outFileStream.WriteLine(vsDoc);
					}
					outFileStream.Write(classReturn);
					if (!isInScopeDef)
						outFileStream.WriteLine("\r\n};");
					else
						outFileStream.WriteLine(";\r\n}");
				} else {
					if (!isInScopeDef)
						outFileStream.Write(name + " = ");
					else
						outFileStream.Write(name + ": ");

					
					outFileStream.Write(that.getType(currentToken["type"]));
				}
				
				
			} else if (currentToken["scope"] !== undefined) {
				if (isInScopeDef)
					outFileStream.WriteLine("};\r\n};");
				var isInScopeDef = true; // for now we will assume that everything from here on is in the same scope... we will see if that is enough
				var name = currentToken["scope"];
				var examplefile = a.Path.substring(0, a.Path.lastIndexOf("\\") + 1) + currentToken["scope"] + ".meta.js";
				if (that.debug)
					WScript.Echo(examplefile);
				outFileStream.WriteLine("/**");
				if (that.fs.FileExists(examplefile)) {
					var exFile = that.fs.GetFile(examplefile);
					var exampleFileStream = exFile.OpenAsTextStream(1);
					outFileStream.WriteLine(" * @description ");
					while (!exampleFileStream.AtEndOfStream) {
						outFileStream.WriteLine(" * " + exampleFileStream.ReadLine());
					}
					outFileStream.WriteLine(" * ");
				} 
				outFileStream.WriteLine(" * @namespace " + currentToken["namespace"]);
				outFileStream.WriteLine("*/");
				outFileStream.WriteLine(name + " = function () {");
				outFileStream.WriteLine(cComment);
				outFileStream.WriteLine("return {");
				omitNextComma = true;
			}
			if (!omitNextComma && (i < currentComment - 1 && isInScopeDef))
				outFileStream.Write(",");
		}
		if (isInScopeDef) {
			outFileStream.WriteLine("};\r\n};");
		}
		
		
		outFileStream.Close();
		
		if (that.objectList.length > 0 && that.createVSDoc) {
			outFileStream = that.fs.CreateTextFile(path + "requiremock.js");
			for (var obj in that.objectList) {
				outFileStream.WriteLine("/// <reference path=\""+that.objectList[obj].p+"\" />");
				
			}
			outFileStream.WriteLine("function require(str) {");
			outFileStream.Write("/// <summary>Possible values for string include: ");
			for (var obj in that.objectList) {
				outFileStream.Write(that.objectList[obj].n + ",");
			}
			outFileStream.Write("</summary>\r\n");
			for (var obj in that.objectList) {
				outFileStream.WriteLine("if (str === \"" + that.objectList[obj].n + "\") return "+that.objectList[obj].n+";");
			}
			outFileStream.WriteLine("}");
			outFileStream.Close();
		}
	};
	return that;
}
/*
				
				if (foundtokens["namespace"] !== undefined && namespaceWritten[foundtokens["namespace"]] === undefined) {
					WScript.Echo("Writing Namespace...");
					outFileStream.WriteLine("if ("+foundtokens["namespace"]+" === undefined) {");
					outFileStream.WriteLine(comment);
					outFileStream.WriteLine(foundtokens["namespace"] + " = {};");
					outFileStream.WriteLine("}");
					namespaceWritten[foundtokens["namespace"]] = true;
					continue;
				}
				if (foundtokens["class"] !== undefined || foundtokens["scope"] !== undefined || foundtokens["memberof"] !== undefined) {
					outFileStream.WriteLine(comment);
					if (that.debug) {
						WScript.Echo("class: " + foundtokens["class"]);
						WScript.Echo("scope: " + foundtokens["scope"]);
						WScript.Echo("memberof: " + foundtokens["memberof"]);
						WScript.Echo("namespace: " + foundtokens["namespace"]);
					}
					var name = foundtokens["class"] || foundtokens["scope"];
					var classReturn = "";
					if (name !== undefined)
						classReturn = "";
					if (foundtokens["returns"] !== undefined)
						classReturn = "return " + that.getType(foundtokens["returns"]);
					
					if (foundtokens["memberof"] !== undefined) {
						name = foundtokens["memberof"].replace(/#/, "");
						name = name + ".prototype." + foundtokens["name"];
					}
					if (that.debug)
						WScript.Echo(name);
					
					var vsDoc = that.getVSDoc(foundtokens, params);
					
					if (foundtokens["type"] === undefined || (foundtokens["class"] !== undefined || foundtokens["scope"] !== undefined)) {
						outFileStream.Write(name + " = function(");
						that.utils.writeParams(outFileStream, params);
						outFileStream.WriteLine(") {");
						if (that.createVSDoc) {
							if (that.debug)
								WScript.Echo(vsDoc);
							outFileStream.WriteLine(vsDoc);
						}
						outFileStream.WriteLine(classReturn);
						outFileStream.WriteLine("};");
					} else {
						outFileStream.Write(name + " = ");
						outFileStream.Write(that.getType(foundtokens["type"]));
						
					}
				}*/
if (WScript.Arguments.length < 1) {
	WScript.Echo("Wrong number of arguments:");
	WScript.Echo("[script.js] file1.js file2.js ...");
	WScript.Echo("[script.js] folder1 folder2 ...");
	WScript.Echo("[script.js] folder1 file1.js folder2 ...");
	WScript.Echo("A new file will be created with the name [file].out.js!");
	
} else {
	var createnew = true;
	var parser = JSDocParser(createVSDoc, debug);
	
	for (var i = 0; i < WScript.Arguments.length; ++i) {
		WScript.Echo(WScript.Arguments(i));
		var outfile = "";
		var infile = "";
		var examplefile = "";
		if (WScript.Arguments(i).indexOf(".js") !== -1 && WScript.Arguments(i).indexOf(".out.js") === -1 && WScript.Arguments(i).indexOf(".dontparse.js") === -1 && WScript.Arguments(i).indexOf(".meta.js") === -1) {
			infile = WScript.Arguments(i);
			outfile = WScript.Arguments(i).replace(".js", ".out.js");
			examplefile = WScript.Arguments(i).replace(".js", ".meta.js");
			parser.processFile(WScript.Arguments(i), outfile, createnew);
		} else if (WScript.Arguments(i).lastIndexOf("\\") === WScript.Arguments(i).length - 1) { // path
			var folder = new Enumerator(parser.fs.GetFolder(WScript.Arguments(i)).Files);
			for (; !folder.atEnd(); folder.moveNext()) {
				if (folder.item().Name.indexOf(".dontparse.js") === -1 && folder.item().Name.indexOf(".out.js") === -1 && folder.item().Name.indexOf(".meta.js") === -1 && folder.item().Name.indexOf(".js") !== -1) {
					outfile = folder.item().Name.replace(".js", ".out.js");
					examplefile = folder.item().Name.replace(".js", ".meta.js");
					WScript.Echo(WScript.Arguments(i) + folder.item().Name + "/" + outfile + "/" + examplefile);
					//try {
					parser.processFile(WScript.Arguments(i) + folder.item().Name, WScript.Arguments(i) + outfile, createnew, WScript.Arguments(i));
					//} catch (e) {}
				}
			}
		}
	}
}
WScript.Echo("Done!");
WScript.Quit(1);